import java.io.*;
import java.net.*;

public class Connection {
    private final String LOG_FILE_PREFIX = "logs/connection_";

    private Socket socket = null;
    private ObjectOutputStream Transmitter = null;
    private ConnectionReceiver Receiver = null;
    private Logger log = null;

    public Connection() {}

    public Connection(Socket s) {
        initConnection(s);
    }
    
    public Connection(String IP, int port, int timeout) {
        initConnection(IP, port, timeout);
    }

    // *************************************************************************
    //          Methods using the established connection
    // *************************************************************************
    public boolean sendData(Pack p) {
        try {
            Transmitter.writeObject(p);
            Transmitter.flush();
        } catch (IOException ioe) {
            log.log(ioe);
            return false;
        }
        
        log.log("\n::Sent Pack::");
        log.log(p.toString());
        return true;
    }
    
    public Pack[] recieveData() {
        Pack[] p;
        if(Receiver.ReceivedData.isEmpty()) {
            return null;
        }
        
        int len = Receiver.ReceivedData.size();
        p = new Pack[len];
        int i = 0;
        while(i < len) {
            p[i] = Receiver.ReceivedData.poll();
            i++;
        }
        
        return p;
    }

    public boolean closeConnection() {
        // To be used when a close connection notification has been received
        return closeConnection("", false);
    }

    public boolean closeConnection(String reason) {
        return closeConnection(reason, true);
    }

    public boolean closeConnection(String reason, boolean notify) {
        if(notify) {
            sendData(new Pack(Pack.CLOSE_CONNECTION_ID, reason, myIP(), theirIP()));
        }

        Receiver.TerminateConnection = true;
        try {
            Transmitter.close();
            socket.close();
        } catch (IOException ioe) {
            log.log(ioe);
            return false;
        }

        Receiver = null;
        Transmitter = null;
        socket = null;

        log.log("Close connection was successful.");
        log.log("Reason for connection shutdown: '" + reason + "'");
        return true;
    }

    // *************************************************************************
    //          Utility methods
    // *************************************************************************
    
    public String myIP() {
        if(socket != null) {
            return socket.getLocalAddress().toString();
        } else {
            log.log("Requested myIP() when socket == null.");
        }
        
        return null;
    }
    
    public String theirIP() {
        if(socket != null) {
            return socket.getInetAddress().toString();
        } else {
            log.log("Requested theirIP() when socket == null.");
        }
        
        return null;
    }
    
    public int myPort() {
        if(socket != null) {
            return socket.getLocalPort();
        } else {
            log.log("Requested myPort() when socket == null.");
        }
        
        return -1;
    }
    
    public int theirPort() {
        if(socket != null) {
            return socket.getPort();
        } else {
            log.log("Requested theirPort() when socket == null.");
        }
        
        return -1;
    }

    // *************************************************************************
    //          Getters & Setters
    // *************************************************************************
    
    // Effectively a setter for the private socket object
    public boolean initConnection(Socket s) {
    // For server, using ServerSocket get socket for connection
        log = new Logger(LOG_FILE_PREFIX + s.getLocalPort() + ".log");
        log.log("Connection Object started (with socket object parsed).");
        this.socket = s;
        
        if(initStreams()) {
            log.log("Successfully initialised connection streams.");
        } else {
            log.log("Could not initialised connection streams.");
            return false;
        }
        
        return true;
    }

    // Effectively a setter for the private socket object
    public boolean initConnection(String IP, int port, int timeout) {
    // For client create a socket based on IP & port
        log = new Logger(LOG_FILE_PREFIX + port + ".log");
        log.log("Connection Object started with parsed parameters:\nIP: '" + IP + "'\nPort: '" + port + "'\nTimeout: '" + timeout + "'\n");
        try {
            this.socket = new Socket(IP, port);
            log.log("Socket object successfully created with parsed parameters.");
        } catch (UnknownHostException une) {
            log.log(une);
            socket = null;
            return false;
        } catch (IOException ioe) {
            log.log(ioe);
            socket = null;
            return false;
        }
        
        try {
            if(socket != null) {
                socket.setSoTimeout(timeout);
                log.log("Socket timeout successfully set to " + timeout + "Successfully finished socket object creation from parsed parameters.");
            }
        } catch (SocketException se) {
            log.log(se);
            return false;
        }
        
        if(initStreams()) {
            log.log("Successfully initialised connection streams.");
        } else {
            log.log("Could not initialised connection streams.");
            return false;
        }
        
        return true;
    }
    
    private boolean initStreams() {
        try {
            Transmitter = new ObjectOutputStream(socket.getOutputStream());
            Receiver = new ConnectionReceiver(new ObjectInputStream(socket.getInputStream()), log);
            Receiver.start();
        } catch (IOException ioe) {
            log.log(ioe);
            return false;
        }
        
        return true;
    }
    
    public Socket getSocket() {
        return socket;
    }
    
    public boolean isConnected() {
        if(socket != null) {
            return socket.isConnected();
        }
        
        return false;
    }
}

